import argparse
import functools
import os
import json
import logging
import re
import sys
import time
import threading
import typing
from gn_common_tool import GnCommonTool
from functools import partial
from concurrent.futures import Future, ThreadPoolExecutor
from multiprocessing import Process, Queue


class GnInfoCollectTool:
    __ohos_shared_library_lock = threading.RLock()
    __shared_library_lock = threading.RLock()
    __ohos_executable_lock = threading.RLock()
    __executable_lock = threading.RLock()
    __ohos_prebuilt_etc_lock = threading.RLock()
    # 存储结果的字典
    __gn_info_list = {
        "ohos_shared_library": list(),
        "shared_library": list(),
        "ohos_executable": list(),
        "executable": list(),
        "ohos_prebuilt_etc": list()
    }
    # 各种template的名字，用做key
    __template_tuple = (
        "ohos_shared_library",
        "shared_library",
        "ohos_executable",
        "executable",
        "ohos_prebuilt_etc"
    )
    # 匹配各种类型的关键内容
    __pattern_for_target = {
        "ohos_shared_library": r"ohos_shared_library\(\"(.*?)\"\)",
        "shared_library": r"shared_library\(\"(.*?)\"\)",
        "ohos_executable": r"ohos_executable\(\"(.*?)\"\)",
        "executable": r"executable\(\"(.*?)\"\)",
        "ohos_prebuilt_etc": r"source *= *\"(.*?)\"",
    }
    # 进行段落匹配的正则
    __pattern_paragraph = {
        "ohos_shared_library": r"^( *)ohos_shared_library\(\".*?\"\)[\s|\S]*?\n\1\}$",
        "shared_library": r"^( *)shared_library\(\".*?\"\)[\s|\S]*?\n\1\}$",
        "ohos_executable": r"^( *)ohos_executable\(\".*?\"\)[\s|\S]*?\n\1\}$",
        "executable": r"^( *)executable\(\".*?\"\)[\s|\S]*?\n\1\}$",
        "ohos_prebuilt_etc": r"^( *)ohos_prebuilt_etc\(\".*?\"\)[\s|\S]*?\n\1\}$",
    }

    #
    # TODO要判断每个需要的值里面是否包含变量
    @staticmethod
    def __is_gn_variable(target: str):
        """
        判断target（应是一个=右边的东西）是否是gn中的变量:
        b = "xxx"
        c = b
        c = "${b}"
        """
        target = target.strip()
        if target.startswith('"') and target.endswith('"'):
            target = target.strip('"')
            if target.startswith("${") and target.endswith("}"):
                return True
            return False
        else:
            return True



    @staticmethod
    def __find_part_subsystem_from_bundle(gnpath: str, stop_tail: str = "home") -> tuple:
        """
        根据BUILD.gn的全路径，一层层往上面查找bundle.json文件，
        并从bundle.json中查找part_name和subsystem
        """
        filename = "bundle.json"
        part_name = None
        subsystem_name = None
        if stop_tail not in gnpath:
            logging.error("{} not in {}".format(stop_tail, gnpath))
            return part_name, subsystem_name
        if os.path.isfile(gnpath):
            gnpath = os.path.split(gnpath)[0]
        while not gnpath.endswith(stop_tail):
            bundle_path = os.path.join(gnpath, filename)
            if not os.path.isfile(bundle_path):  # 如果该文件不在该目录下
                gnpath = os.path.split(gnpath)[0]
                continue
            # 该文件在目录下
            with open(bundle_path, 'r', encoding='utf-8') as f:
                content = json.load(f)
                try:
                    part_name = content["component"]["name"]
                    subsystem_name = content["component"]["subsystem"]
                except KeyError:
                    logging.warning(
                        "not found component/name or component/subsystem in bundle.json")
                finally:
                    break
        return part_name, subsystem_name

    @staticmethod
    def __find_part_subsystem(part_name_pattern: re.Pattern, subsystem_pattern: re.Pattern, gn_content: str,
                              gn_file: str,
                              project_path: str) -> tuple:
        """
        从gn的content中对应的part_name和subsystem
        如果在gn中找不到，就到bundle.json中去找
        """
        part_name = None
        subsystem_name = None
        part_var_flag = False  # 标识这个变量从gn中取出的原始值是不是变量
        subsystem_var_flag = False
        var_list = list()
        part = re.search(part_name_pattern, gn_content)
        subsystem = re.search(subsystem_pattern, gn_content)
        if part is not None and subsystem is not None:
            part = part.group(1)
            subsystem = subsystem.group(1)

        if len(part) != 0 and (part.isspace() is False):
            part_name = part.strip()
            if GnInfoCollectTool.__is_gn_variable(part_name):
                part_var_flag = True
                var_list.append(part_name)
        subsystem = os.popen(subsystem_cmd).read(
        ).strip().split("=")[-1].strip().strip("\"")
        if len(subsystem) != 0 and (subsystem.isspace() is False):
            subsystem_name = subsystem.strip()
            if GnInfoCollectTool.__is_gn_variable(subsystem_name):
                subsystem_var_flag = True
                var_list.append(subsystem_name)
        if part_var_flag and subsystem_var_flag:
            part_name, subsystem_name = GnInfoCollectTool.__find_variables_in_gn(
                tuple(var_list), gn_file, project_path)
        elif part_var_flag:
            part_name = GnInfoCollectTool.__find_variables_in_gn(
                (part_name,), gn_file, project_path)[0]
        elif subsystem_var_flag:
            subsystem_name = GnInfoCollectTool.__find_variables_in_gn(
                (subsystem_name,), gn_file, project_path)[0]
        if part_name is not None and subsystem_name is not None:
            return part_name, subsystem_name
        logging.info(
            "part_name or subsystem not found in BUILD.gn, now try to find it from bundle.json")
        # 如果有一个没有找到，就要一层层去找bundle.json文件
        part_name, subsystem_name = GnInfoCollectTool.__find_part_subsystem_from_bundle(
            gn_file, stop_tail=project_path)
        return part_name, subsystem_name

    # 处理
    @staticmethod
    def process_project_gn(project_path: str, black_keywords: tuple = tuple(), black_dirs: tuple = tuple()):
        gn_file_list = GnInfoCollectTool.find_files(project_path=project_path, target_filename="BUILD.gn",
                                                    black_keywords=black_keywords, black_dirs=black_dirs)
        pass

    @staticmethod
    def __find_target_name_in_paragraph(target_pattern: re.Pattern, content: str):
        """
        默认不为空
        """
        return re.search(target_pattern, content).group(1)

    @staticmethod
    def __find_part_subsystem_info_in_content(part_pattern: re.Pattern,
                                              subsystem_pattern: re.Pattern,
                                              content: str) -> tuple:
        """
        这里的content可以是段落，也可以是正片文字
        """
        part_name = None
        subsystem_name = None
        part = re.search(part_pattern, content)
        if part is not None:
            part_name = part.group(1)
        subsystem = re.search(subsystem_pattern, content)
        if subsystem is not None:
            subsystem_name = subsystem.group(1)
        return part_name, subsystem_name

    @staticmethod
    def __process_single_gn_target(type: str, gn_path: str, target_pattern: re.Pattern, part_pattern: re.Pattern,
                                   subsystem_pattern: re.Pattern,
                                   project_path: str):
        """
        处理单个文件中的单个type
        在这一步就要将数据写入结果中
        """
        with open(gn_path, 'r', encoding='utf-8') as f:
            content = f.read()
            # 从文件内容中匹配段落
            target_paragraph_list = GnInfoCollectTool.findall_paragraph(GnInfoCollectTool.__pattern_paragraph.get(type),
                                                                        content)
            if len(target_paragraph_list) == 0:
                # 如果长度为0，则表明文件中没有目标代码段
                return
            # 如果长度不为0，才继续往下走
            # 先从content中随便抓个part_name和subsystem_name备用
            part_name_gn, subsystem_name_gn = GnInfoCollectTool.__find_part_subsystem_info_in_content(part_pattern,
                                                                                                      subsystem_pattern,
                                                                                                      content)
            func = functools.partial(GnInfoCollectTool.__find_variables_in_gn, path=gn_path, stop_tail=project_path)
            if GnInfoCollectTool.__is_gn_variable(part_name_gn) and GnInfoCollectTool.__is_gn_variable(
                    subsystem_name_gn):  # 如果是变量：1. 有引号和${}，2. 无引用
                part_name_gn, subsystem_name_gn = func((part_name_gn, subsystem_name_gn))
            elif GnInfoCollectTool.__is_gn_variable(part_name_gn):
                part_name_gn = func((part_name_gn,))[0]
            elif GnInfoCollectTool.__is_gn_variable(subsystem_name_gn):
                subsystem_name_gn = func((subsystem_name_gn,))[0]
            # 到此为止，如果两个都确实存在，则应当已经找到了他们的值
            if part_name_gn is None and subsystem_name_gn is None:
                # 如果两个都是None，则说明此gn文件中压根没有part_name和subsystem_name，下面就没有必要在段落中抓了，直接到bundle.json中找
                part_name_gn, subsystem_name_gn = GnInfoCollectTool.__find_part_subsystem_from_bundle(gn_path,
                                                                                                      project_path)
            for pgh in target_paragraph_list:
                target_name = GnInfoCollectTool.__find_target_name_in_paragraph(target_pattern, pgh)
                if GnInfoCollectTool.__is_gn_variable(target_name):
                    # 如果是变量
                    pass

    @staticmethod
    def __target_processor(type: str, gn_file_list: list, project_path: str):
        """
        处理target资源
        """
        target_pattern = re.compile(GnInfoCollectTool.__pattern_for_target.get(type))
        # part_name和subsystem_name，如果有引号的话是包括了引号的
        # 只用于gn的content的匹配
        part_pattern = re.compile(r"part_name *= *(.*?)\n")
        subsystem_pattern = re.compile(r"subsystem_name *= *(.*?)\n")
        for gn_file in gn_file_list:
            GnInfoCollectTool.__process_single_gn_target(type, gn_file, target_pattern, part_pattern, subsystem_pattern,
                                                         project_path)

    # TODO 每种processor直接处理多个gn_file，便于使用多线程
    @staticmethod
    def __etc_processor(gn_gn_file_list: list, project_path: str):
        """
        处理etc资源
        """

    @staticmethod
    def __init_gn_info_dict():
        for t in GnInfoCollectTool.__template_tuple:
            GnInfoCollectTool().__gn_info_dict.get(t)["others"] = list()

    # TODO etc资源的处理需要修改，包括模板和具体的处理方法
    @staticmethod
    def collect_gn_info(project_path: str):
        """
        在BUILD.gn中循环grep几种pattern
        对每一条找到的进行详细处理：
        包括查找target或资源名称、查找part_name与subsystem、分析出具体的gnfile以及用pattern定位到的行号
        """
        grep_struct = (
            ("ohos_shared_library", r"ohos_shared_library\(\".*\"\)",
             GnInfoCollectTool.__target_processor),
            ("shared_library", r"shared_library\(\".*\"\)", GnInfoCollectTool.__target_processor),
            ("ohos_executable", r"ohos_executable\(\".*\"\)",
             GnInfoCollectTool.__target_processor),
            ("executable", r"executable\(\".*\"\)", GnInfoCollectTool.__target_processor),
            ("ohos_prebuilt_etc", r"^ *source *= *\".*\"", GnInfoCollectTool.__etc_processor),
        )

        black_keywords = ("test", "import\(")  # 用于过滤grep出的文件
        black_dirs = (".repo", ".ccache", "doc", "test", "build",
                      "out")  # 给grep gn --exclude用的
        json_file = "gn_info.json"
        GnInfoCollectTool.__init_gn_info_dict()
        # for i in range(len(grep_struct)):
        #     output = GnInfoCollectTool.grep((grep_struct[i][1],),
        #                            grep_path=project_path,
        #                            exclude=black_dirs,
        #                            black_keyword=black_keywords)
        #     for line in output:
        #         target_name, part_name, subsystem, gn_path, line_no = grep_struct[i][2](
        #             line.strip(), project_path)
        #         info = dict()
        #         info["name"] = target_name
        #         info["part_name"] = part_name
        #         info["subsystem"] = subsystem
        #         info["gn_path"] = gn_path
        #         info["line_no"] = line_no
        #         GnInfoCollectTool.__gn_info_dict.get(grep_struct[i][0]).append(info)
        #
        #         logging.info("[target: {} part_name: {} subsystem: {} gn_path: {} line_no: {}]".format(
        #             target_name, part_name, subsystem, gn_path, line_no
        #         ))
        process = partial(GnInfoCollectTool.__process_template, project_path=project_path,
                          black_keywords=black_keywords,
                          black_dirs=black_dirs)
        with ThreadPoolExecutor(max_workers=len(grep_struct) + 1) as pool:
            pool.map(process, grep_struct)
        with open(json_file, 'w', encoding='utf-8') as f:
            f.write(json.dumps(GnInfoCollectTool.__gn_info_dict, indent=4))

    @staticmethod
    def __process_template(ele_tuple: tuple, project_path: str, black_keywords: tuple, black_dirs: tuple):
        print("processing: {}".format(ele_tuple[0]))
        output = GnInfoCollectTool.__common_tool.grep((ele_tuple[1],),
                                                      grep_path=project_path,
                                                      exclude=black_dirs,
                                                      black_keyword=black_keywords)
        for line in output:
            info = ele_tuple[2](
                line.strip(), project_path)
            # info = dict()
            # info["name"] = target_name
            # info["part_name"] = part_name
            # info["subsystem"] = subsystem
            # info["gn_path"] = gn_path
            # info["line_no"] = line_no
            # GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).append(info)
            if info.get("subsystem") is None:
                GnInfoCollectTool.__gn_info_dict.get(
                    ele_tuple[0]).get("others").append(info)
                continue
            if GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(info.get("subsystem")) is None:
                GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0])[info.get("subsystem")] = dict()
                GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(
                    info.get("subsystem"))["others"] = list()
            if info.get("part_name") is None:
                GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(
                    info.get("subsystem")).get("others").append(info)
                continue
            if GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(info.get("subsystem")).get(
                    info.get("part_name")) is None:
                GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(
                    info.get("subsystem"))[info.get("part_name")] = list()
            GnInfoCollectTool.__gn_info_dict.get(ele_tuple[0]).get(
                info.get("subsystem")).get(info.get("part_name")).append(info)
            logging.info(info)


def test():
    black_keywords = ("test", "import\(")  # 用于过滤grep出的文件
    black_dirs = (".repo", ".ccache", "doc", "test", "build",
                  "out")  # 给grep gn --exclude用的
    gn_files = GnInfoCollectTool.find_files(
        ".", black_keyword=black_keywords, black_dir=black_dirs)
    print(gn_files)
    print(len(gn_files))


"""
思路：
把所有的gn文件传给几种模板的处理器
"""


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--project_path", default=".", type=str,
                        help="root path of openharmony")
    args = parser.parse_args()
    project_path = os.path.abspath(args.project_path)
    logging.getLogger().setLevel(logging.ERROR)
    if sys.platform != "linux":
        logging.error("script only support linux")
        sys.exit(-1)
    start = time.time()
    GnInfoCollectTool.collect_gn_info(project_path)
    print("total time: {}".format(time.time() - start))


if __name__ == '__main__':
    # test()
    main()
